﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace DARE
{
    public class CWaypoints
    {
        #region identifier

        public enum EType
        {
            CURVES,
            LINES
        };

        #endregion

        #region fields

        private CSplineGroup m_spline;
        private EType m_wayType = EType.LINES;
        private List<Vector3> m_points;
        private float m_totalTime;
        private float m_startTime;
        private float m_endTime;
        private float m_pauseTime;
        private int m_direction = 1;
        private bool m_isStarted;
        private bool m_pause;
        private float m_totalDistance = 0;

        #endregion

        #region properties

        public float TotalDuration { get { return m_totalTime; } set { m_totalTime = value; } }
        public EType WayType
        {
            get { return m_wayType; }
            set
            {
                m_wayType = value;
                if (m_wayType == EType.CURVES)
                    GenerateSplineGroup();
            }
        }

        #endregion

        #region ctor

        public CWaypoints()
        {
            m_points = new List<Vector3>();
            m_startTime = 0;
            m_totalDistance = 0;
            m_totalTime = 0;
            m_endTime = 0;
            m_pause = false;
            m_isStarted = false;
            m_pause = false;
            m_spline = new CSplineGroup();
        }

        #endregion

        #region methods

        public void GenerateSplineGroup()
        {
            m_spline.Splines.Clear();
            Vector3[] tab = new Vector3[4];
            int i;
            int k = 0;
            for (i = 0; i < m_points.Count; ++i)
            {
                if (i % 4 == 0 && i > 0)
                {
                    m_spline.Splines.Add(new CSpline(4, tab));
                    tab[0] = Vector3.Zero;
                    tab[1] = Vector3.Zero;
                    tab[2] = Vector3.Zero;
                    tab[3] = Vector3.Zero;
                    k = 0;
                }
                tab[k] = m_points[i];
                ++k;
            }
            if (i % 4 != 0)
                for (int j = 3; j >= (i % 4); --j)
                    tab[j] = tab[k - 1];
            CSpline spl = new CSpline(4, tab);
            m_spline.Splines.Add(spl);
        }

        public float ComputeDistance()
        {
            float dist = 0;
            for (int i = 1; i < m_points.Count; ++i)
                dist += Vector3.Distance(m_points[i - 1], m_points[i]);
            return dist;
        }

        public CWaypoints ClearPointList()
        {
            m_points.Clear();
            m_totalDistance = 0;
            GenerateSplineGroup();
            return this;
        }

        public CWaypoints AddPoint(Vector3 point)
        {
            m_points.Add(point);
            m_totalDistance = ComputeDistance();
            GenerateSplineGroup();
            return this;
        }

        public CWaypoints RemovePoint(int pos)
        {
            m_points.RemoveAt(pos);
            m_totalDistance = ComputeDistance();
            GenerateSplineGroup();
            return this;
        }

        public CWaypoints RemovePoint(Vector3 point)
        {
            m_points.Remove(point);
            m_totalDistance = ComputeDistance();
            GenerateSplineGroup();
            return this;
        }

        private Vector3 GetLinePosition(float time)
        {
            if (m_points.Count < 1)
                return Vector3.Zero;
            else if (m_points.Count == 1)
                return m_points.First();
            time = time - m_startTime;
            float pos = time / m_totalTime;
            float dist = 0;
            float tmp = 0;
            int i;
            if (m_direction > 0)
                i = 1;
            else
                i = m_points.Count - 1;
            for (; i < m_points.Count && i > 0; i += m_direction)
            {
                tmp = Vector3.Distance(m_points[i - 1], m_points[i]);
                if ((dist + tmp) / m_totalDistance > pos)
                    break;
                dist += tmp;
            }
            if (i >= m_points.Count)
                return m_points.Last();
            else if (i <= 0)
                return m_points.First();
            Vector3 directorVector = m_points[i] - m_points[i - 1];
            directorVector.Normalize();
            if (m_direction < 0)
            {
                directorVector *= m_direction;
                ++i;
            }
            return m_points[i - 1] + (directorVector * ((m_totalDistance * pos) - dist));
        }

        private Vector3 GetCurvePosition(float time)
        {
            return m_spline.GetPosition(m_startTime, m_endTime, time, m_direction);
        }

        public Vector3 GetPosition(float time)
        {
            if (!m_isStarted && m_direction > 0)
                return m_points.First();
            else if (!m_isStarted)
                return m_points.Last();
            if (m_pause)
                time = m_pauseTime;
            if (time > m_endTime && m_direction > 0)
                return m_points.Last();
            else if (time > m_endTime)
                return m_points.First();
            switch (m_wayType)
            {
                case EType.LINES:
                    return GetLinePosition(time);
                case EType.CURVES:
                    return GetCurvePosition(time);
            }
            return Vector3.Zero;
        }

        public void StartMovement(float currentTime)
        {
            if (currentTime > m_endTime)
                m_isStarted = false;
            if (m_isStarted && m_pause)
                m_startTime += currentTime - m_pauseTime;
            else if (!m_isStarted)
                m_startTime = currentTime;
            m_endTime = m_startTime + m_totalTime;
            m_pause = false;
            m_isStarted = true;
        }

        public void PauseMovement(float currentTime)
        {
            if (m_pause)
                return;
            m_pauseTime = currentTime;
            m_pause = true;
        }

        public void StopMovement()
        {
            m_isStarted = false;
            m_pause = false;
        }

        public void ReverseMovement(float currentTime)
        {
            m_direction *= -1;
            if (m_isStarted)
            {
                if (m_pause)
                    m_startTime += currentTime - m_pauseTime;
                if (currentTime < m_endTime)
                    m_endTime = currentTime + (currentTime - m_startTime);
                else
                    m_isStarted = false;
                m_startTime = m_endTime - m_totalTime;
            }
        }


        #endregion
    }
}
